#include <QtGui>
#include <QtOpenGL>

#include <iostream>
#include <math.h>
#include "glwidget.h"
#include "Piece.h"

GLuint GLWidget::tex [4];

//! [0]
GLWidget::GLWidget(QWidget *parent)
    : QGLWidget(QGLFormat(QGL::DoubleBuffer), parent)
{
	rapportAffichage = 16/10.0 ;

	modeSelectionPiece = false ;
	modeSelectionPosition = false ;
	modeRotation = true ;
	
	currentPlayer = false ;
	
	timer = new QTimer(this) ;
	angleRotation = 180 ;
	dangleRotation = 0 ;
	xrotation = 0 ;
	pasRotation = 50 ;
	dx = M_PI / pasRotation  ;
	sensRotation = false ;
	
	connect(timer, SIGNAL(timeout()), this, SLOT(rotateScene()));
}
//! [0]

//! [1]
GLWidget::~GLWidget()
{
	delete timer ;
    makeCurrent();
}
//! [1]
// Ceci est une méthode
void GLWidget::setListPiece (std::list<Piece*> list){
	listPieces = list ;
}
// Ceci est un SLOT qui appel la méthode
void GLWidget::setPieces (std::list<Piece*> pieces){
	setListPiece(pieces) ;
	updateGL() ;
}

std::list<Piece*> GLWidget::getListPiece () const{
	return listPieces ;
}

//! [2]
QSize GLWidget::minimumSizeHint() const
{
    return QSize(50, 50);
}
//! [2]

//! [3]
QSize GLWidget::sizeHint() const
//! [3] //! [4]
{
    return QSize(800, 500);
}
//! [4]

void GLWidget::initializeGL()
{
	try{
		PPMImage *noir;
		PPMImage *blanc;
		PPMImage *p1;
		PPMImage *p2;
		char texFileName1 [] = "textnoir.ppm";
		noir = new PPMImage(texFileName1);
		char texFileName2 [] = "textblanc.ppm";
		blanc = new PPMImage(texFileName2);
		char texFileName3 [] = "plateau.ppm";
		p1 = new PPMImage(texFileName3);
		char texFileName4 [] = "deco.ppm";
		p2 = new PPMImage(texFileName4);
		glEnable(GL_DEPTH_TEST);
		
		glGenTextures(4,tex);
					glBindTexture(GL_TEXTURE_2D, tex[1]);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, noir->sizeX, noir->sizeY,
			 0, GL_RGB, GL_UNSIGNED_BYTE, noir->data);
	   
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, noir->sizeX, noir->sizeY,
			  GL_RGB, GL_UNSIGNED_BYTE, noir->data);
					glBindTexture(GL_TEXTURE_2D, tex[0]);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, blanc->sizeX, blanc->sizeY,
			 0, GL_RGB, GL_UNSIGNED_BYTE, blanc->data);
	   
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, blanc->sizeX, blanc->sizeY,
			  GL_RGB, GL_UNSIGNED_BYTE, blanc->data);
		
		
		glBindTexture(GL_TEXTURE_2D, tex[2]);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, p1->sizeX, p1->sizeY,
			 0, GL_RGB, GL_UNSIGNED_BYTE, p1->data);
	   
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, p1->sizeX, p1->sizeY,
			  GL_RGB, GL_UNSIGNED_BYTE, p1->data);
	
		glBindTexture(GL_TEXTURE_2D, tex[3]);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, p2->sizeX, p2->sizeY,
			 0, GL_RGB, GL_UNSIGNED_BYTE, p2->data);
	   
		gluBuild2DMipmaps(GL_TEXTURE_2D, 3, p2->sizeX, p2->sizeY,
			  GL_RGB, GL_UNSIGNED_BYTE, p2->data);

		glEnable(GL_TEXTURE_2D);  
		
		delete noir ;
		delete blanc ;
		delete p1 ;
		delete p2 ;
    }
    catch(int e){
    	std::cout << "Impossible de charger les textures" << std::endl ;
   	}
}
//! [6]
inline void GLWidget::setProjection() {
	//glOrtho(-rapportAffichage/2, rapportAffichage/2, -0.5, 0.5, 3.0, 15.0);
	double ew = 2 ; // A Généraliser !!!!!!!
	glFrustum(-rapportAffichage*ew/2, rapportAffichage*ew/2, -0.5*ew, 0.5*ew, 3.0, 25.0);

}
//! [7]
void GLWidget::paintGL()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glClearColor(0.8, 0.8, 0.8, 0);
    glLoadIdentity();
	//gluLookAt(0.5, 3.3, 1, 0.5, 0, -0.5, 0, 1, 0) ;
	double ew = 8;// A Généraliser !!!!!!!
	gluLookAt(ew/2, 10, ew/2, ew/2, 0, -ew/2, 0, 1, 0) ;

	glTranslated(ew/2, 0, -ew/2) ;
	glRotated(angleRotation, 0, 1, 0) ;
	glTranslated(-ew/2, 0, ew/2) ;
	
	glPushMatrix();

		monPlateau.drawPlateau() ;
		//monPlateau.drawCases() ;
		
		glDisable(GL_TEXTURE_2D) ;
		if(modeSelectionPosition)
			monPlateau.drawCasesSelected(currentPositions) ;
		glEnable(GL_TEXTURE_2D) ;
		for (std::list<Piece*>::iterator iter = listPieces.begin(); iter != listPieces.end(); iter++)
		{
			glPushMatrix() ;
				PieceGL * p ((*iter)->getObject());
				p->movetoPosition((*iter)->getPosition().getC(), (*iter)->getPosition().getL()) ;
				p->draw() ;
			glPopMatrix() ;
		}

	glPopMatrix() ;
	
	glFlush() ;
}
//! [7]

//! [8]
void GLWidget::resizeGL(int width, int height)
{
    int side = qMin((int)(width/rapportAffichage), height);
    glViewport((width-side*rapportAffichage)/2, (height - side) / 2, side*rapportAffichage, side);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    setProjection();
    glMatrixMode(GL_MODELVIEW);
}
//! [8]	

//! [9]
void GLWidget::mousePressEvent(QMouseEvent *event)
{

	bool mspiece (modeSelectionPiece), msPos (modeSelectionPosition) ;
	if (mspiece){
		PieceGL * laPiece (NULL) ;
		laPiece = pickPiece(event->x(), event->y(), currentPlayer) ;
		if (laPiece)
		emit pieceSelected(laPiece) ;
	}

	if (msPos){
		Position * laPosition (NULL) ;
		laPosition = pickPosition(event->x(), event->y(), currentPositions);
		if (laPosition)
		emit positionSelected(laPosition) ;
	}

}
//! [9]

inline bool isPositionJouable (const Position &p, std::list<Position>& posJouables){
	bool jouable = false ;
	for (std::list<Position>::iterator laPos = posJouables.begin(); laPos != posJouables.end() && !jouable; laPos++)
	{
		jouable = (*laPos) == p ;
	}
	
	return jouable ;
}

Position * GLWidget::pickPosition(int x, int y, std::list<Position>& posJouables){ // A factoriser
	int BUFSIZE = 512 ;
	GLuint selectBuf[BUFSIZE];
	GLint hits;
	GLint viewport[4];
	
	//on récupère le viewport courant
	glGetIntegerv (GL_VIEWPORT, viewport);
	
	glSelectBuffer (BUFSIZE, selectBuf);
	(void) glRenderMode (GL_SELECT);
	
	//Initialisation de la pile des noms
	glInitNames();
	glPushName(0);
	
	glMatrixMode (GL_PROJECTION);
	glPushMatrix ();
	glLoadIdentity ();
	/*  create 10x10 pixel picking region near cursor location	*/
	gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y), 
				   1.0, 1.0, viewport);
	//On doit avoir la même projection en mode sélection qu'en mode rendu :
    setProjection();
	
	glMatrixMode (GL_MODELVIEW);
	
	//On calcule la scène pour le picking : elle n'est pas dessinée dans un tampon chromatique
	monPlateau.drawCasesPicking() ;
	
	glMatrixMode (GL_PROJECTION);
	glPopMatrix ();
	glMatrixMode (GL_MODELVIEW);
	
	//On s'assure que les ordre OpenGL sont exécutés
	glFlush ();
	
	hits = glRenderMode (GL_RENDER);
	
	//On traite les sommets cliqués
	int i ;
	unsigned int j;
	GLuint names, *ptr;
	ptr = (GLuint *) selectBuf;
	Position * maPosition = NULL ;
	float nearest = 1000 ; //crade
	for (i = 0; i < hits; i++) {	/*  for each hit  */
		names = *ptr;
		ptr++;
		float z1 = (float) *ptr/0x7fffffff;
		ptr++;
		float z2 = (float) *ptr/0x7fffffff ;
		float z = (z1+z2)/2 ;
		ptr++;
		for (j = 0; j < names; j++) {	/*  for each name */
		//	printf ("%i ", *ptr);
			if (z < nearest){
				nearest = z ;
				maPosition = (Position *)*ptr ;
			}
			ptr++; 
		}
	}
	if (!(maPosition && isPositionJouable(*maPosition, posJouables))){
		maPosition = NULL ;
	}
	updateGL();
	return maPosition ;
}

PieceGL * GLWidget::pickPiece(int x, int y, bool couleur){ // A factoriser
	int BUFSIZE = 512 ;
	GLuint selectBuf[BUFSIZE];
	GLint hits;
	GLint viewport[4];
	
	//on récupère le viewport courant
	glGetIntegerv (GL_VIEWPORT, viewport);
	
	glSelectBuffer (BUFSIZE, selectBuf);
	(void) glRenderMode (GL_SELECT);
	
	//Initialisation de la pile des noms
	glInitNames();
	glPushName(0);
	
	glMatrixMode (GL_PROJECTION);
	glPushMatrix ();
	glLoadIdentity ();
	/*  create 10x10 pixel picking region near cursor location	*/
	gluPickMatrix ((GLdouble) x, (GLdouble) (viewport[3] - y), 
				   1.0, 1.0, viewport);
	//On doit avoir la même projection en mode sélection qu'en mode rendu :
    setProjection();

	glMatrixMode (GL_MODELVIEW);
	
	//On calcule la scène pour le picking : elle n'est pas dessinée dans un tampon chromatique
//	monPlateau.drawPicking() ;
	for (std::list<Piece*>::iterator iter = listPieces.begin(); iter != listPieces.end(); iter++)
	{
		glPushMatrix() ;
		PieceGL * p ((*iter)->getObject());
		p->movetoPosition((*iter)->getPosition().getC(), (*iter)->getPosition().getL()) ;
		p->drawPicking() ;
		glPopMatrix() ;
	}
	
	glMatrixMode (GL_PROJECTION);
	glPopMatrix ();
	glMatrixMode (GL_MODELVIEW);
	
	//On s'assure que les ordre OpenGL sont exécutés
	glFlush ();
	
	hits = glRenderMode (GL_RENDER);
	
	//On traite les sommets cliqués
	int i;
	unsigned int j;
	GLuint names, *ptr;
	ptr = (GLuint *) selectBuf;
	PieceGL * maPiece = NULL;
	float nearest = 1000 ; //crade
	for (i = 0; i < hits; i++) {	/*  for each hit  */
		names = *ptr;
		ptr++;
		float z1 = (float) *ptr/0x7fffffff;
		ptr++;
		float z2 = (float) *ptr/0x7fffffff ;
		float z = (z1+z2)/2 ;
		ptr++;
		for (j = 0; j < names; j++) {	/*  for each name */
			//printf ("%X ", *ptr);
			if (z < nearest && ((PieceGL*)(*ptr))->getCouleur()==couleur){
				nearest = z ;
				maPiece = (PieceGL*)(*ptr) ;
			}
			ptr++;
		}
	}
	if (maPiece)
		maPiece->toggleSelect() ;	
	updateGL();
	return maPiece ;
}

void GLWidget::pieceSelectionOn (bool player) {
	modeSelectionPiece = true ;
	currentPlayer = player ;
}

void GLWidget::pieceSelectionOff () {
	modeSelectionPiece = false ;
}

void GLWidget::positionSelectionOn (const std::list<Position> & positions) {
	modeSelectionPosition = true ;
	currentPositions = positions ;
	updateGL() ;
}

void GLWidget::positionSelectionOff () {
	modeSelectionPosition = false ;
}

void GLWidget::rotateScene() {
	if (sensRotation){
		dangleRotation =-90.0*sin(xrotation)*dx ;
		xrotation += dx ;
		
		angleRotation += dangleRotation ;
		if (xrotation > 2*M_PI){
			angleRotation = 180 ;
			xrotation = 0 ;
			timer->stop() ;
			sensRotation = ! sensRotation ;
		}
	}
	else{
		dangleRotation =-90.0*sin(xrotation)*dx ;
		xrotation += dx ;

		angleRotation += dangleRotation ;
		if (xrotation > M_PI){
			angleRotation = 0 ;
			xrotation = M_PI ;
			timer->stop() ;
			sensRotation = ! sensRotation ;
		}
	}
	updateGL() ;
}

void GLWidget::toggleScene() {
	timer->start(1000/pasRotation) ;
}

